#include <linux/module.h>
#include <linux/fs.h>
#include <linux/security.h>
#include <linux/sysctl.h>
#include <linux/kallsyms.h>
#include <linux/kprobes.h>

struct security_operations *gsp_security_ops = NULL; //系统中当前启用的lsm策略
struct security_operations gs_security_ops;          //用于保存系统中启用的lsm策略

static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
    return 0;
}

static struct kprobe kp = {
    .symbol_name = "kallsyms_lookup_name",
};

typedef unsigned long (*kallsyms_lookup_name_t)(const char *name);
static kallsyms_lookup_name_t fn_kallsyms_lookup_name = NULL;

typedef char *(*dentry_path_t)(struct dentry *dentry, char *buf, int buflen);
static dentry_path_t fn_dentry_path;

int get_kallsyms_lookup_name(void)
{
    int ret = -1;

    kp.pre_handler = handler_pre;
    // register_kprobe成功返回0
    ret = register_kprobe(&kp);
    if (ret < 0)
    {
        printk(KERN_ERR "register_kprobe failed, returned %d\n", ret);
        return ret;
    }

    printk(KERN_INFO "Planted fn_kallsyms_lookup_name at %p\n", kp.addr);
    fn_kallsyms_lookup_name = (kallsyms_lookup_name_t)(void *)kp.addr;
    unregister_kprobe(&kp);
    return ret;
}

static int geek_task_kill(struct task_struct *p, struct siginfo *info, int sig, u32 secid)
{
    char filename[256], *f;
    unsigned pid, tgid;

    if ((sig == SIGKILL || sig == SIGTERM) && p != NULL && p->mm != NULL)
    {
        task_lock(p);
        f = d_path(&p->mm->exe_file->f_path, filename, sizeof(filename));
        pid = p->pid;
        tgid = p->tgid;
        task_unlock(p);

        if (!IS_ERR(f))
        {
            printk(KERN_INFO "send signal [%d] to task [%s] pid [%d] tgid [%d]\n", sig, f, pid, tgid);
            if (!strncmp(f, "/usr/sbin/rsyslogd", 18))
            {
                printk(KERN_ERR "send signal [%d] to task [%s] pid [%d] tgid [%d] deny!!!\n", sig, f, pid, tgid);
                return -EACCES;
            }
        }
    }

    return 0;
}

int geek_inode_permission(struct inode *inode, int mask)
{
    struct dentry *dentry;
    char filename[256], *f;

    if (!special_file(inode->i_mode) && (mask & MAY_WRITE))
    {
        dentry = d_find_alias(inode);
        if (dentry == NULL)
        {
            printk(KERN_ERR "d_find_alias lookup failed\n");
            return 0;
        }

        f = fn_dentry_path(dentry, filename, sizeof(filename));
        if (IS_ERR(f))
        {
            printk(KERN_ERR "fn_dentry_path failed\n");
            return 0;
        }

        printk(KERN_INFO "file %s may write\n", f);
    }
    return 0;
}

static struct security_operations geek_ops = {
    .name = "geek",
    .task_kill = geek_task_kill,
    .inode_permission = geek_inode_permission,
};

static __init int init_hook(void)
{
    int ret;

    printk(KERN_INFO "-----------lsm hook init start--------------\n");

    ret = get_kallsyms_lookup_name();
    if (ret < 0 || fn_kallsyms_lookup_name == NULL)
    {
        return ret;
    }

    fn_dentry_path = (dentry_path_t)fn_kallsyms_lookup_name("dentry_path");
    if (fn_dentry_path == NULL)
    {
        printk(KERN_ERR "dentry_path lookup failed\n");
        return -1;
    }

    //security_module_enable(&geek_ops);
    ret = register_security(&geek_ops);
    if (ret < 0)
    {
        printk(KERN_INFO "register_security ret %d\n", ret);
        gsp_security_ops = *(struct security_operations **)fn_kallsyms_lookup_name("security_ops");
        if (gsp_security_ops == NULL)
        {
            return ret;
        }

        //保存系统中默认的策略
        memcpy(&gs_security_ops, gsp_security_ops, sizeof(struct security_operations));
        printk(KERN_INFO "gsp_security_ops %p name %s\n", gsp_security_ops, gsp_security_ops->name);

        //替换系统中默认的策略
        gsp_security_ops->task_kill = geek_task_kill;
        gsp_security_ops->inode_permission = geek_inode_permission;
    }

    return 0;
}

static __exit void exit_hook(void)
{
    printk("exit_lsm_hook\n");
    //恢复之前的lsm策略
    memcpy(gsp_security_ops, &gs_security_ops, sizeof(struct security_operations));
}

module_init(init_hook);
module_exit(exit_hook);

MODULE_LICENSE("Dual BSD/GPL");
